package cli;

import common.Constants.*;

import common.GridImpl;
import common.cell.Cell;
import common.cell.Empty;

typedef Mutation = {
    x: Int,
    y: Int,
    z: Int,
    to: Cell,
    triggerMoved: Bool,
}

class Grid implements GridImpl {
    public var tilearr: Array<Array<Cell>> = [];
    private var charset: Array<String>;

    private var doBatch = false;
    private var batchMutations: Array<Mutation> = [];

    public function new() {
        newEmptyLayer(LAYER_MAP);
        newEmptyLayer(LAYER_ENTITIES);
        newEmptyLayer(LAYER_GUI);
    }

    public function batch() {
        doBatch = true;
    }

    public function execBatch() {
        doBatch = false;

        for (mutation in batchMutations) {
            setCell(mutation.x, mutation.y, mutation.z, mutation.to, mutation.triggerMoved);
        }

        batchMutations = [];
    }

    function xyIndex(x: Int, y: Int): Int {
        if (x >= GRID_WIDTH || y >= GRID_HEIGHT) throw 'Out of bounds: ($x, $y)';

        return y * GRID_WIDTH + x;
    }

    public function newEmptyLayer(z: Int) {
        // Fill the new layer with Empty cells.
        tilearr[z] = [];

        for (y in 0...GRID_HEIGHT) {
            for (x in 0...GRID_WIDTH) {
                tilearr[z].push(new Empty(this, x, y, z));
            }
        }
    }

    public function setCell(x: Int, y: Int, z: Int, to: Cell, triggerMoved = true) {
        if (doBatch) {
            batchMutations.push({ x: x, y: y, z: z, to: to, triggerMoved: triggerMoved });
            return;
        }

        var index = xyIndex(x, y);

        tilearr[z][index].destroyed();
        tilearr[z][index] = to;

        to.x = x;
        to.y = y;
        to.z = z;
        if (triggerMoved) to.moved();
    }

    public function getCell(x: Int, y: Int, z: Int): Cell {
        return tilearr[z][xyIndex(x, y)];
    }

    public function print() {
        var buf = "";

        for (y in 0...GRID_HEIGHT) {
            for (x in 0...GRID_WIDTH) {
                // We want the cell at (x, y) with the highest Z position that
                // is not Empty.
                var cell: Cell = new Empty(this, x, y, -1);

                for (layer in tilearr) {
                    if (!Std.is(layer[xyIndex(x, y)], Empty)) {
                        cell = layer[xyIndex(x, y)];
                    }
                }

                var colorCode = switch cell.color {
                    case 0: 31; // red
                    case 1: 32; // green
                    case 2: 33; // yellow
                    case 3: 34; // blue
                    case 4: 35; // magenta
                    case 5: 36; // cyan
                    case 6: 39; // white
                    default: throw "Invalid cell.color";
                };

                buf += '\x1B[${colorCode}m';
                buf += cell.char;
            }

            buf += "\n";
        }

        Sys.print(buf);
        Sys.print("\x1B[0m"); // reset
    }
}
